

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ThreadFactory;

import org.ourgrid.threadServices.MonitoredThreadFactory;
import org.ourgrid.threadServices.ThreadServicesFactory;



public class Executor {
	
	private static BufferedReader myReader;
	private final int HELP = -1;
	private final int EXIT = 0;
	private final int SHOW = 1;
	private final int START = 2;
	private final int POWERON = 3;
	private final int POWEROFF = 4;
	private final int UNKNOW = -127;
	
	// arg 0 sera router.config e arg 1 sera o enlace.config 
	public static void main(String[] args) {
		
		Executor me = new Executor();
		me.run(args[0] , args[1]);			
		
	}
	
	public void run( String routersconfig , String enlacesconfig ){

		//threads
		MonitoredThreadFactory monitoredThreadFactory = new MonitoredThreadFactory("ripsim");
		Map<Byte,Thread> threads = new HashMap<Byte,Thread>();
				
		
		
		Map<Byte,Router> routers = new HashMap<Byte,Router>();
		Map<Byte,List<EnlaceCost>> enlaces = new HashMap<Byte,List<EnlaceCost>>();
		
		
		
		
		try {					
			String line = "";
			
			//READ router.config
			myReader = new BufferedReader(new FileReader(routersconfig));
			while (myReader.ready()) {
				line = myReader.readLine();
				if (line.length() > 0 && !line.startsWith("#")){
					Router r = createRouter(line);
					routers.put(r.getId(), r);
				}					
			}
			
			//READ enlace.config			
			myReader = new BufferedReader(new FileReader(enlacesconfig));
			while (myReader.ready()) {
				line = myReader.readLine();
				if (line.length() > 0 && !line.startsWith("#"))
					getEnlacesCost(line,enlaces);
			}			
		
			//associate 
			configRouter(routers,enlaces);			
			
			//set up threadservice
			setUpRouter(routers,monitoredThreadFactory,threads);			
			
						
			myReader = new BufferedReader(new InputStreamReader(System.in));
			while ( true ) {
				
				System.out.print("simrouter# ");
				
				StringTokenizer stringTokenizer = new StringTokenizer(myReader.readLine());
				
				line = stringTokenizer.nextToken();
				int command = getComand(line);
								
				
				switch ( command ){
					case EXIT : 
						System.exit(0) ; 
						break;
					case SHOW :
						showRouters(routers) ; 
						break;
					case START : 
						startAllRouters(threads); 
						break;
					case POWERON :
						String router = stringTokenizer.nextToken();					
						startRouter(Byte.parseByte(router),monitoredThreadFactory,threads,routers);
						break;
					case POWEROFF :
						router = stringTokenizer.nextToken();
						if ( router.equalsIgnoreCase("all") )
							stopAllRouters(routers);
						else
							stopRouter(Byte.parseByte(router),routers);
						break;
					case HELP :
						help();
						break;
					default: 
						System.out.println("unknow command !!!");
						help(); 
					break;
				}
				
				
					
			}
			
			
		} catch (FileNotFoundException e) {
			System.err.println("Config file not found!");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("I/O error");
			System.exit(1);
		}
	}

	private void help() {
		System.out.println("comand list [ exit , show , poweron , poweroff , help , start]");
		System.out.println("exit : exit simrouter.");
		System.out.println("start : start all routers.");
		System.out.println("show : show all routers.");
		System.out.println("poweroff : turn off a router ( #poweroff rouder_id ) or poweroff all routers ( #poweroff all ).");
		System.out.println("poweron : turn on a router ( #poweron router_id ).");
		System.out.println("help : this command.");
	}

	private int getComand(String line) {
		if ( line.equalsIgnoreCase("exit") )
			return EXIT;
		if ( line.equalsIgnoreCase("show") )
			return SHOW;
		if ( line.equalsIgnoreCase("poweron") )
			return POWERON;
		if ( line.equalsIgnoreCase("poweroff") )
			return POWEROFF;
		if ( line.equalsIgnoreCase("help") )
			return HELP;
		if ( line.equalsIgnoreCase("start") )
			return START;

		return UNKNOW;
	}

	private void startRouter(byte b, MonitoredThreadFactory threadfactory,Map<Byte, Thread> threads, Map<Byte, Router> routers) {
		Thread t = threads.get(b);
		t = threadfactory.newThread( routers.get(b) );
		t.start();
	}

	private void stopRouter(byte b, Map<Byte, Router> routers) {
		routers.get(b).shutdown();
	}

	private void stopAllRouters(Map<Byte, Router> routers) {		
		for (Router  r : routers.values()) {
			r.shutdown();
		}
	}

	private static void startAllRouters(Map<Byte, Thread> threads) {
		for ( Thread t: threads.values()){
			t.start();
		}
	}
	
	/**
	 * show all routers
	 * 
	 * @param routers
	 */
	private static void showRouters(Map<Byte, Router> routers ) {
		for (Router r : routers.values())
			System.out.println(r);
	}	
	
	//assoiate a thread to one router
	private static void setUpRouter(Map<Byte,Router> routers,MonitoredThreadFactory threadfactory,
			Map<Byte,Thread> threads){		
		
		for ( Router r : routers.values() ){			
			threads.put(r.getId(),  threadfactory.newThread(r));			
		}		
		
		
	}
	
	private static void configRouter(Map<Byte,Router> routers , Map<Byte,List<EnlaceCost>> enlaces) throws SocketException{
		
		int numberOfRouters = routers.size();
		
		for ( Router r : routers.values() ){			
			
			List<EnlaceCost> enlaceCostList = enlaces.get(r.getId());			
			
			int[ ] distanceVector = new int[numberOfRouters];
			InetSocketAddress[ ] neighbors = new InetSocketAddress[numberOfRouters];				
			
			// fill neigbors and distanceVector with no route to host
			for (int i = 0; i < neighbors.length; i++) {
				neighbors[i] = Router.NOT_NEIGHBOR;
				distanceVector[i] = Router.NOT_NEIGHBOR_COST;
			}
			
			neighbors[r.getId()] = Router.LOCALHOST;
			distanceVector[r.getId()] = Router.LOCALHOST_COST;

			for (EnlaceCost cost : enlaceCostList) {
				
				byte routerID = cost.getRouter();
				Router neigborRoute = routers.get(routerID);					
				distanceVector[routerID] = cost.getCost();					
				neighbors[routerID] = new InetSocketAddress(neigborRoute.getIp(),neigborRoute.getPort());
				
			}

			
			r.setInitialEnlaceState(distanceVector,neighbors);			 
			
			
		}
		
	}
	
	private static Router createRouter(String router) throws SocketException {
				
		StringTokenizer stringTokenizer = new StringTokenizer(router);
		
		if ( stringTokenizer.countTokens() != 3 )
			System.exit(1);
		
		byte id = Byte.parseByte(stringTokenizer.nextToken());		
		int port = Integer.parseInt(stringTokenizer.nextToken());
		String ip = stringTokenizer.nextToken();		
		
		return new Router(id,port,ip);

	}
		
	private static void getEnlacesCost(String line,Map<Byte,List<EnlaceCost>> routers) throws SocketException {
		
		StringTokenizer stringTokenizer = new StringTokenizer(line);
		
		//assert ( stringTokenizer.countTokens() != 3 );			
		
		byte id_router1 = Byte.parseByte(stringTokenizer.nextToken());
		byte id_router2 = Byte.parseByte(stringTokenizer.nextToken());
		int cost = Integer.parseInt(stringTokenizer.nextToken());		 
		
		List<EnlaceCost> router1_list = routers.get(id_router1);
		List<EnlaceCost> router2_list = routers.get(id_router2);
		
		if ( router1_list == null ){
			
				router1_list = new LinkedList<EnlaceCost>();
				routers.put(id_router1,router1_list);
		}
		
		router1_list.add(new EnlaceCost(id_router2,cost));
		
		if ( router2_list == null ){
			
			router2_list = new LinkedList<EnlaceCost>();
			routers.put(id_router2,router2_list);
	}
	
		router2_list.add(new EnlaceCost(id_router1,cost));

	}
	
	
	private static class EnlaceCost {
		private int cost;
		private byte router;
		
		public EnlaceCost( byte r , int c ){
			cost = c;
			router = r;
		}
		
		public int getCost(){
			return cost;
		}
		
		public void setCost(int c){
			this.cost = c;
		}
		
		public byte getRouter(){
			return router;
		}
		
		public void setRouter(byte r){
			this.router = r;
		}
		public String toString(){
			return " router : " + router + " cost : " + cost;
		}
		
	}
	public Executor(){
		
	}
}